Yahoo!翻訳
Yahoo! JAPAN - Yahoo!翻訳 - 使い方 - ヘルプ
crosslanguage
Yahoo! JAPANとページ内のコンテンツとの関連はありません。 原文のページは、こちらから確認できます。
※英単語をダブルクリックすると、別ウィンドウで辞書検索を表示します。(リンクの設定されたテキストは除く)
翻訳設定
翻訳方向:
結果表示:
翻訳技術提供:株式会社クロスランゲージ

How FriendFeed uses MySQL to store schema-less data
FriendFeedは、どのように、スキーマのないデータを格納するために、MySQLを使いますか

By Bret Taylor · February 27, 2009
ブレットテイラーによって · 2009年2月27日

Background
背景

We use MySQL for storing all of the data in FriendFeed. Our database has grown a lot as our user base has grown. We now store over 250 million entries and a bunch of other data, from comments and "likes" to friend lists.
我々は、データの全てをFriendFeedに保存するために、MySQLを使います。我々のユーザー主成分が成長して、我々のデータベースはたいへん成長しました。コメントと「好み」から友人リストまで、我々は現在2億5000万以上のエントリとたくさんの他のデータを格納します。

As our database has grown, we have tried to iteratively deal with the scaling issues that come with rapid growth. We did the typical things, like using read slaves and memcache to increase read throughput and sharding our database to improve write throughput. However, as we grew, scaling our existing features to accomodate more traffic turned out to be much less of an issue than adding new features.
我 々のデータベースが成長して、我々はiterativelyに急成長とともに来るスケーリング問題に対処しようとしました。我々は典型的ことをしました、 読まれたスループットを増やすために読まれた奴隷とmemcacheを使って、よくなるために我々のデータベースをshardingすることの様に、ス ループットを書いてください。しかし、我々が成長したので、より多くの交通をaccomodateするために我々の既存の特徴を登ることは問題の加わるこ とより非常に少ないことがわかりました 新機能。

In particular, making schema changes or adding indexes to a database with more than 10 - 20 million rows completely locks the database for hours at a time. Removing old indexes takes just as much time, and not removing them hurts performance because the database will continue to read and write to those unused blocks on every
特 に、スキーマ変化をもたらすか、インデックスをデータベースに10以上と加えます - 2000万は、完全にロックをこぎます何時間も一度にデータベース。古いインデックスを削除することはちょうど同じくらいの時間をとります、そして、デー タベースがあらゆるものの上でそれらの使っていないブロックに読み書きし続けるので、彼らを追い出さないことはパフォーマンスを傷つけます INSERT
挿入
, pushing important blocks out of memory. There are complex operational procedures you can do to circumvent these problems (like setting up the new index on a slave, and then swapping the slave and the master), but those procedures are so error prone and heavyweight, they implicitly discouraged our adding features that would require schema/index changes. Since our databases are all heavily sharded, the relational features of MySQL like
記 憶から重要なブロックを押すこと。あなたがこれらの問題(奴隷の上で新しいインデックスを準備して、それから、奴隷とマスターを交換することのように)を 回避するためにすることができる複雑な操作上の手順があります、しかし、それらの手順はそううつ伏せのエラーとヘビー級です、彼らは暗黙のうちに、スキー マ/インデックス変化を必要とする我々の追加特徴をはばみました。我々のデータベースがMySQLのすべてのかなりshardedされた、関係の特徴であ るので、好きにしてください JOIN
参加...
have never been useful to us, so we decided to look outside of the realm of RDBMS.
我々にとってこれまで有用にしてはいけないので、我々はRDBMSの領域の外で見ることに決めました。

Lots of projects exist designed to tackle the problem storing data with flexible schemas and building new indexes on the fly (e.g., CouchDB). However, none of them seemed widely-used enough by large sites to inspire confidence. In the tests we read about and ran ourselves, none of the projects were stable or battle-tested enough for our needs (see this somewhat outdated article on CouchDB, for example). MySQL works. It doesn't corrupt data. Replication works. We understand its limitations already. We like MySQL for storage, just not RDBMS usage patterns.
たくさんのプロジェクトが、柔軟なスキーマをデータにたくわえていて、自動的にその場で(例えばCouchDB) 新しいインデックスを構築している問題に取り組むようにできて存在します。しかし、彼らの誰も、信頼を吹き込むために、広い敷地によって十分に広く使われ ているようでありませんでした。我々が読んで、我々自身走らせたテストでは、プロジェクトのどれも、安定でなかったか、我々のニーズ(たとえば、CouchDBに関するこのいくぶん時代遅れの記事を見てください)のために、十分に戦いの検査済みでありませんでした。MySQLは働きます。それは、データを壊しません。複製は働きます。我々は、すでにその限界を理解します。我々は、保管(ちょっとRDBMS使用パターンでない)のために、MySQLが好きです。

After some deliberation, we decided to implement a "schema-less" storage system on top of MySQL rather than use a completely new storage system. This post attempts to describe the high-level details of the system. We are curious how other large sites have tackled these problems, and we thought some of the design work we have done might be useful to other developers.
い くらかの熟考の後、我々は完全に新しい記憶装置を使うよりはむしろ、MySQLの上で「スキーマのない」記憶装置を実装することに決めました。このポスト は、システムの高水準詳細を記述しようとします。我々は他の広い敷地がどのようにこれらの問題に取り組んだかについて知りたいです、そして、我々は我々が したデザイン作業のいくつかが他の開発者にとって有用かもしれないと思いました。

Overview
概要

Our datastore stores schema-less bags of properties (e.g., JSON objects or Python dictionaries). The only required property of stored entities is
我々のデータストアは、特性(例えばJSON物またはパイソン辞書)のスキーマのないバッグを保存します。保存された実体の唯一の必須の財産は、そうです id
ID
, a 16-byte UUID. The rest of the entity is opaque as far as the datastore is concerned. We can change the "schema" simply by storing new properties.
16バイトのUUID。データストアが関係する限り、残りの実体は不透明です。我々は、単に新しい特性を保存することによって、「スキーマ」を変えることができます。

We index data in these entities by storing indexes in separate MySQL tables. If we want to index three properties in each entity, we will have three MySQL tables - one for each index. If we want to stop using an index, we stop writing to that table from our code and, optionally, drop the table from MySQL. If we want a new index, we make a new MySQL table for that index and run a process to asynchronously populate the index without disrupting our live service.
我 々は、インデックスを別々のMySQLテーブルに格納することによって、これらの実体でデータにインデックスを付けます。我々が各々の実体で3つの特性に インデックスを付けたいならば、我々は3つのMySQLテーブルを持っています - 各々のインデックスのための1。我々がインデックスを使用するのを止めたいならば、我々は我々のコードからそのテーブルに書くのを止めて、任意に、 MySQLからテーブルを落とします。我々が新しいインデックスが欲しいならば、我々はそのインデックスのために新しいMySQLテーブルを作って、我々 の有効なサービスを中断させることなく非同期でインデックスに住むためにプロセスを走らせます。

As a result, we end up having more tables than we had before, but adding and removing indexes is easy. We have heavily optimized the process that populates new indexes (which we call "The Cleaner") so that it fills new indexes rapidly without disrupting the site. We can store new properties and index them in a day's time rather than a week's time, and we don't need to swap MySQL masters and slaves or do any other scary operational work to make it happen.
そ の結果、我々は結局我々が前に持ったより多くのテーブルを持っていることになります、しかし、インデックスを加えて、削除することは簡単です。我々は、そ れがサイトを崩壊させることなく速く新しいインデックスを満たすように、新しいインデックス(それを我々は「Cleaner」と呼びます)に住むプロセス を重く最適化しました。我々は新しい特性を保存することができて、一日の時間に一週間の時間よりむしろ彼らにインデックスを付けることができます、そし て、我々はMySQLマスターと奴隷を交換する必要はないか、それを起こらせるために他のどの怖い操作上の仕事もしません。

Details
詳細

In MySQL, our entities are stored in a table that looks like this:
MySQLに、我々の実体は、これのように見えるテーブルに保管されます:

CREATE TABLE entities (
    added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    id BINARY(16) NOT NULL,
    updated TIMESTAMP NOT NULL,
    body MEDIUMBLOB,
    UNIQUE KEY (id),
    KEY (updated)
) ENGINE=InnoDB;

CREATE表実体(added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY(ID BINARY(16) NOT NULL)は、TIMESTAMP NOT NULL、体MEDIUMBLOB、UNIQUE KEY(ID)を更新しました、 キー(更新されます) )ENGINE=InnoDB;

The
added_id
added_id
column is present because InnoDB stores data rows physically in primary key order. The
InnoDBが身体的にデータ列を主要な重要な順序に保管するので、コラムは存在します。 AUTO_INCREMENT
AUTO_INCREMENT
primary key ensures new entities are written sequentially on disk after old entities, which helps for both read and write locality (new entities tend to be read more frequently than old entities since FriendFeed pages are ordered reverse-chronologically). Entity bodies are stored as zlib-compressed, pickled Python dictionaries.
主要な鍵により確実に新しい実体が古い実体の後で順番に椎間板に書かれることになります。そして、それは両方の読込み及び書込み場所(新しい実体は、 FriendFeedページから古い実体がリバース年代順に命じられるよりしばしば読まれる傾向があります)のために助けます。実体体は、zlib圧縮し た、塩漬けにしたパイソン辞書として保存されます。

Indexes are stored in separate tables. To create a new index, we create a new table storing the attributes we want to index on all of our database shards. For example, a typical entity in FriendFeed might look like this:
イ ンデックスは、別々のテーブルに格納されます。新しいインデックスを作成するために、我々は我々が我々のデータベース破片の全ての上でインデックスに欲し い特質を保存している新しいテーブルをつくります。たとえば、FriendFeedの典型的実体は、これのように見えるかもしれません:

{
    "id": "71f0c4d2291844cca2df6f486e96e37c",
    "user_id": "f48b0440ca0c4f66991c4d5f6a078eaf",
    "feed_id": "f48b0440ca0c4f66991c4d5f6a078eaf",
    "title": "We just launched a new backend system for FriendFeed!",
    "link": "http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",
    "published": 1235697046,
    "updated": 1235697046,
}

{ 「ID」:「71f0c4d2291844cca2df6f486e96e37c」、「user_id」:「f48b0440ca0c4f66991c4d5f6a078eaf」、「feed_id」:「f48b0440ca0c4f66991c4d5f6a078eaf」、「タイトル」:「我々は、ちょうどFriendFeedのために新しいバックエンドシステムを開始しました!」、「関連」:「http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",は「公表しました」:1235697046(「更新される」):1235697046}

We want to index the
我々は、インデックスに欲しいです user_id
user_id
attribute of these entities so we can render a page of all the entities a given user has posted. Our index table looks like this:
我々が所定のユーザーが掲示したすべての実体のページを提出することができるように、これらの実体のものと考えてください。我々のインデックステーブルは、これのように見えます:

CREATE TABLE index_user_id (
    user_id BINARY(16) NOT NULL,
    entity_id BINARY(16) NOT NULL UNIQUE,
    PRIMARY KEY (user_id, entity_id)
) ENGINE=InnoDB;

CREATE表index_user_id(user_id BINARY(16) NOT NULL、entity_id BINARY(16) NOT NULL UNIQUE、PRIMARY KEY(user_id、entity_id))ENGINE=InnoDB;

Our datastore automatically maintains indexes on your behalf, so to start an instance of our datastore that stores entities like the structure above with the given indexes, you would write (in Python):
我々のデータストアは自動的にインデックスのあなたのためで生活を維持するので、伝えられたインデックスで上記の構造のような実体を保存する我々のデータストアの例を始めるために、あなたは書くでしょう(パイソンで):

user_id_index = friendfeed.datastore.Index(
    table="index_user_id", properties=["user_id"], shard_on="user_id")
datastore = friendfeed.datastore.DataStore(
    mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"],
    indexes=[user_id_index])

new_entity = {
    "id": binascii.a2b_hex("71f0c4d2291844cca2df6f486e96e37c"),
    "user_id": binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"),
    "feed_id": binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"),
    "title": u"We just launched a new backend system for FriendFeed!",
    "link": u"http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",
    "published": 1235697046,
    "updated": 1235697046,
}
datastore.put(new_entity)
entity = datastore.get(binascii.a2b_hex("71f0c4d2291844cca2df6f486e96e37c"))
entity = user_id_index.get_all(datastore, user_id=binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"))

=friendfeed.datastore.DataStore、=friendfeed.datastore.Index user_id_index(table="index_user_id」、properties=‖[「user_id」]、shard_on="user_id」)データストア(mysql_shards=[「127.0.0.1:3306」、「127.0.0.1:3307」]、indexes=[user_id_index]) new_entity ={ 「ID」:binascii.a2b_hex(「71f0c4d2291844cca2df6f486e96e37c」)、「user_id」:binascii.a2b_hex(「f48b0440ca0c4f66991c4d5f6a078eaf」)、「feed_id」:binascii.a2b_hex(「f48b0440ca0c4f66991c4d5f6a078eaf」)、「タイトル」:u"Weは、ちょうどFriendFeedのために新しいバックエンドシステムを開始しました!」、「関連」:u"http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c」(「発表される」):1235697046(「更新される」):1235697046} datastore.put(new_entity)実体= datastore.get(binascii.a2b_hex(「71f0c4d2291844cca2df6f486e96e37c」))実体= user_id_index.get_all(データストア(user_id=binascii.a2b_hex(「f48b0440ca0c4f66991c4d5f6a078eaf」)))

The
Index
インデックス
class above looks for the
上記のクラスは、探します user_id
user_id
property in all entities and automatically maintains the index in the
すべての実体で資産、そして、自動的に中でインデックスを維持する index_user_id
index_user_id
table. Since our database is sharded, the
テーブル。我々のデータベースがshardedされるので、 shard_on
shard_on
argument is used to determine which shard the index gets stored on (in this case,
議論は、インデックスがどの破片を保存させるかについて確定するのに用いられます(この場合、 entity["user_id"] % num_shards
実体[「user_id」]% num_shards
).
)。

You can query an index using the index instance (see
あなたはインデックス例を使っているインデックスについてたずねることができます(見てください user_id_index.get_all
user_id_index.get_all
above). The datastore code does the "join" between the
より上に)。データストアコードは、「接点」をします index_user_id
index_user_id
table and the
テーブル、そして、 entities
実体
table in Python, by first querying the
パイソンでテーブル、たずねることことで index_user_id
index_user_id
tables on all database shards to get a list of entity IDs and then fetching those entity IDs from the
実体IDとその時のリストをそれらの実体IDを取ってくるようにするすべてのデータベース破片の上でテーブル entities
実体
table.
テーブル。

To add a new index, e.g., on the
例えば、新しいものを加えることはインデックスを付けますの上で link
リンク
property, we would create a new table:
資産、我々は新しいテーブルをつくります:

CREATE TABLE index_link (
    link VARCHAR(735) NOT NULL,
    entity_id BINARY(16) NOT NULL UNIQUE,
    PRIMARY KEY (link, entity_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE表index_link(関連VARCHAR(735) NULLでない、entity_id BINARY(16) NOT NULL UNIQUE、PRIMARY KEY(関連、entity_id))ENGINE=InnoDB DEFAULT CHARSET=utf8;

We would change our datastore initialization code to include this new index:
我々は、この新しいインデックスを含むために、我々のデータストア初期化コードを変えます:

user_id_index = friendfeed.datastore.Index(
    table="index_user_id", properties=["user_id"], shard_on="user_id")
link_index = friendfeed.datastore.Index(
    table="index_link", properties=["link"], shard_on="link")
datastore = friendfeed.datastore.DataStore(
    mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"],
    indexes=[user_id_index, link_index])

=friendfeed.datastore.DataStore、=friendfeed.datastore.Index user_id_index(table="index_user_id」、properties=‖[「user_id」]、shard_on="user_id」)=friendfeed.datastore.Index link_index(table="index_link」、properties=‖[「関連」]、shard_on="link」)データストア(mysql_shards=[「127.0.0.1:3306」、「127.0.0.1:3307」]、indexes=[user_id_index、link_index])

And we could populate the index asynchronously (even while serving live traffic) with:
そして、我々は非同期でインデックスに住むことができました‖(生の交通に供給している間さえ)で:

./rundatastorecleaner.py --index=index_link

./rundatastorecleaner.py-index=index_link

Consistency and Atomicity
一貫性と原子価

Since our database is sharded, and indexes for an entity can be stored on different shards than the entities themselves, consistency is an issue. What if the process crashes before it has written to all the index tables?
我々のデータベースがshardedされる、そして、実体のためのインデックスが実体自体より異なる破片の上に格納されることができるので、一貫性は問題です。それがすべてのインデックステーブルに書く前にプロセスがクラッシュするならば、どうですか?

Building a transaction protocol was appealing to the most ambitious of FriendFeed engineers, but we wanted to keep the system as simple as possible. We decided to loosen constraints such that:
業務プロトコルを構築することはFriendFeedエンジニアで最も野心的なものに訴えていました、しかし、我々はシステムをできるだけ単純にしておきたかったです。我々は、制約を緩和することに決めました:

  • The property bag stored in the main
    概して保存される資産バッグ entities
    実体
    table is canonical
    テーブルは、聖職服です
  • Indexes may not reflect the actual entity values
    インデックスは、実際の実体価値を反映しないかもしれません

Consequently, we write a new entity to the database with the following steps:
従って、我々は以下のステップでデータベースに新しい実体を書きます:

  1. Write the entity to the
    実体を書く entities
    実体
    table, using the ACID properties of InnoDB
    InnoDBのACID特性を使用してテーブル
  2. Write the indexes to all of the index tables on all of the shards
    破片の全ての上に、インデックステーブルの全てに、インデックスを書いてください

When we read from the index tables, we know they may not be accurate (i.e., they may reflect old property values if writing has not finished step 2). To ensure we don't return invalid entities based on the constraints above, we use the index tables to determine which entities to read, but we re-apply the query filters on the entities themselves rather than trusting the integrity of the indexes:
我 々がインデックステーブルから読んだとき、我々は彼らが正確でないかもしれない(すなわち、書くことがステップ2を終えなかったならば、彼らは古い財産価 値を熟考するかもしれません)ということを知っています。我々が上の制約に基づく無効な実体を返さないことを確実とするために、我々はどの実体を読むべき かについて決定するためにインデックステーブルを使用します、しかし、我々はインデックスの完全性を信頼することよりむしろ実体自体の上で質問フィルタを 再び用います:

  1. Read the
    読む entity_id
    entity_id
    from all of the index tables based on the query
    質問に基づくインデックステーブルの全てから
  2. Read the entities from the
    実体を読む entities
    実体
    table from the given entity IDs
    所定の実体IDからのテーブル
  3. Filter (in Python) all of the entities that do not match the query conditions based on the actual property values
    質問に実際の財産価値に基づく状況に合うものを見つけない実体の全てをフィルターに通してください(パイソンで)

To ensure that indexes are not missing perpetually and inconsistencies are eventually fixed, the "Cleaner" process I mentioned above runs continously over the entities table, writing missing indexes and cleaning up old and invalid indexes. It cleans recently updated entities first, so inconsistencies in the indexes get fixed fairly quickly (within a couple of seconds) in practice.
イ ンデックスが永久になくなっていない、そして、矛盾が結局固定されることを確実とするために、私が上で言及した「よりきれいな」プロセスは実体テーブルの 上にcontinouslyに動作します。そして、なくなったインデックスを記述して、古くて無効なインデックスをきれいにします。それは最初に最近更新 された実体をきれいにするので、インデックスの矛盾は実際にはかなり速く固定されます(二秒以内に)。

Performance
パフォーマンス

We have optimized our primary indexes quite a bit in this new system, and we are quite pleased with the results. Here is a graph of FriendFeed page view latency for the past month (we launched the new backend a couple of days ago, as you can tell by the dramatic drop):
我 々はこの新しいシステムでかなり我々の主要なインデックスを最適化しました、そして、我々は結果に全く満足します。前月(あなたが劇的な低下によって話す ことができるように、我々は二日前新しいバックエンドを開始しました)の間のFriendFeedページ表示待ち時間のグラフは、ここにあります:

In particular, the latency of our system is now remarkably stable, even during peak mid-day hours. Here is a graph of FriendFeed page view latency for the past 24 hours:
特に、ピークの昼の時間の間にさえ、我々のシステムの待ち時間は、現在著しく安定です。過去24時間のFriendFeedページ表示待ち時間のグラフは、ここにあります:

Compare this to one week ago:
これを1週前と比較してください:

The system has been really easy to work with so far. We have already changed the indexes a couple of times since we deployed the system, and we have started converting some of our biggest MySQL tables to use this new scheme so we can change their structure more liberally going forward.
シ ステムは、ここまで働くのが本当に簡単でした。我々がシステムを展開した時から、我々は2回インデックスをすでに変えました、そして、我々が進んでより自 由に彼らの構造を変えることができるように、我々はこの新しい計画を使うために我々の最大のMySQLテーブルのいくつかを変え始めました。